#! /usr/bin/python
# -*- coding:utf8 -*-

from PyQt4.QtCore import *
from PyQt4.QtGui import *

class Spline(object):
    """Spline类
    该类实现Cardinal样条曲线的生成

    :attention :调用self.spline函数返回QPoint list
    :attention :调用self.m函数，返回矩阵系数矩阵m

    """
    def __init__(self,points, n, grain, tension):
        """初始化

        :param points : 控制点
        :param n: 为控制点数量,
        :param grain: 为控制点之间的插值平滑点,
        :param tension: 为通过控制点位值的曲线平滑程度.

        """
        self.__points = points
        self.__n = n
        self.__grain = grain
        self.__tension = tension

    def spline(self):
        """返回曲线上所有点的坐标

        :return : QPoint list

        """
        points = self.__points
        n = self.__n
        grain = self.__grain
        tension = self.__tension

        tmp_n = n   # 4
        result = []
        result.append(points[0])
        for i in range(tmp_n): # 1, 2, 3,..., tmp_n-1
            result.append(points[i])
        result.append(points[tmp_n-1])
        return self.cubicSpline(tmp_n+2, result, grain, tension)

    def cubicSpline(self, n, points, grain, tension): # n:6, points:6
        """被spline函数调用

        :return : QPoint list

        """
        coefficientMatrix = self.m()
        u = []
        grain = grain + 1
        for i in range(grain):
            u.append((float(i))/grain)
        resultSplineEquation = [] # finally spline math funtion
        for m in range(1, n-2): # 0 , 3  === 4
            i = m - 1
            # resultSplineEquation.append(points[m])
            for j in range(grain):
                tmpPointMatrixX = self.b(points[i].x(), points[i+1].x(),
                        points[i+2].x(), points[i+3].x())
                tmpPointMatrixY = self.b(points[i].y(), points[i+1].y(),
                        points[i+2].y(), points[i+3].y())
                tmpU = [[u[j]*u[j]*u[j], u[j]*u[j], u[j], 1.0]]
                # tmpTmpU = [[u[j]*u[j]*u[j]], [u[j]*u[j]], [u[j]], [1]]
                x = self.calculatePoint(tmpU, coefficientMatrix,
                    tmpPointMatrixX)[0][0]
                y = self.calculatePoint(tmpU, coefficientMatrix,
                    tmpPointMatrixY)[0][0]
                point = QPointF(x, y)
                resultSplineEquation.append(point)
        resultSplineEquation.append(points[n-1])
        return resultSplineEquation


    def calculatePoint(self, tmpU, coefficientMatrix, tmpPointMatrix):
        """计算点坐标

        :param coefficientMatrix: 系数矩阵
        :param tmpPointMatrix: 端点x,y坐标

        """
        tmpResult = self.matrixMultiply(coefficientMatrix, tmpPointMatrix)
        # tmpResult = self.matrixMultiply(tmpU, coefficientMatrix)
        result = self.matrixMultiply(tmpU, tmpResult)
        return result


    def m(self):  # c is a constant, when c is 0.5, the line is 
        """计算矩阵

        :param c: float (tension )
        :return : matrix

        """
        c = self.__tension
        if c:
            m = [[-1.0, 2.0/c-1, -2.0/c+1, 1.0],
                 [2.0, -3.0/c+1, 3.0/c-2, -1.0],
                 [-1.0, 0.0, 1.0, 0.0],
                 [0.0, 1.0/c, 0.0, 0.0]]
            multiply = lambda x: x*c
            m = [[multiply(each) for each in row] for row in m]
            return m
        else:
            print "coefficient cant be zero"
            return 0

    def b(self, y1, y2, y3, y4):
        """构造矩阵并进行矩阵转置

        """
        return [[y1], [y2], [y3], [y4]]

    def matrixMultiply(self, m1, m2):
        """矩阵相乘

        :param m1: 矩阵
        :param m2: 矩阵
        :return : 是具体情况而定

        """
        result = [[sum(a * b for a, b in zip(a, b)) for b in zip(*m2)] for a in m1]
        return result


if __name__ == "__main__":
    points = [QPointF(100.0, 100.0), QPointF(200.0, 200.0)]
    grain = 1
    n = 2
    tension = 0.5
    spline = Spline(points, n, grain, tension)
    result = spline.spline()


    import sys
    app = QApplication(sys.argv)
    scene = QGraphicsScene()
    view = QGraphicsView(scene)
    point_0 = result[0]
    for each in result:
        line = QLineF(point_0, each)
        scene.addLine(line)
        point_0 = each
    view.resize(500, 500)
    view.show()
    sys.exit(app.exec_())







